package xj.graph2d;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import xj.graph2d.TextShape.Mark;
import xj.graph2d.TextShape.Style;
import xj.lang.JavaLangUtil;
import xj.lang.JavaReservedWords;

public class TextMarker { 

  public static final int DEFAULT_STYLE  = 0; 
  public static final int COMMENT        = 1; 
  public static final int STRING_LITERAL = 2; 
  public static final int RESERVED_WORD  = 3; 
  public static final int MODIFIER       = 4; 
  public static final int TYPE           = 5; 
  public static final int NUMBER         = 6; 
  public static final int CONSTANT       = 7; 
  public static final int CLASS_NAME     = 8; 
  public static final int OBJECT_NAME    = 9; 
  public static final int VARIABLE_NAME  = 10; 
  public static final int METHOD_NAME    = 11; 
  public static final int OPERATOR       = 12; 
  public static final int STEREOTYPE     = 13; 
  public static final int EXCEPTION      = 14; 

  public static void drawString(Graphics2D g2, String str, int x, int y) { 
    drawString(g2, str, x, y, -1, -1); 
  }

  public static void drawString(Graphics2D g2, String str, int x, int y,
				int problemStart, int problemEnd) { 
    //System.out.println("TextMarker.drawString() " + str + " [" + problemStart + ":" + problemEnd + "]");

    if (g2 != null && str != null) { 
      int len = str.length();
      int x0 = x; 
      List<Mark> marks = markString(str, 0, len);
      if (marks == null) { 
	g2.drawString(str, x, y);
      } else { 
	Color c = g2.getColor();
	Font f = g2.getFont(); 
	for (Mark mark : marks) { 
	  Style style = getStyle(mark.style); 
	  if (style != null) { 
	    if (style.color != null) { 
	      g2.setColor(style.color); 
	    } else { 
	      g2.setColor(c);
	    }
	    if (style.bold || style.italic) { 
	      int fstyle = 0;
	      if (style.bold) fstyle = Font.BOLD;
	      if (style.italic) fstyle |= Font.ITALIC; 
	      Font font = f.deriveFont(fstyle);
	      g2.setFont(font); 
	    } else { 
	      g2.setFont(f); 
	    }
	  } else { 
	    g2.setColor(c);
	    g2.setFont(f); 
	  }
	  String seg = str.substring(mark.start, mark.end); 
	  g2.drawString(seg, x, y);
	  FontMetrics fm = g2.getFontMetrics();
	  x += fm.stringWidth(seg);
	}
	g2.setFont(f); 
	g2.setColor(c);
      }
      
      if (problemStart >= 0 && 
	  problemEnd >= 0 && 
	  problemStart < len && 
	  problemEnd <= len &&
	  problemStart < problemEnd) { 
	FontMetrics fm = g2.getFontMetrics();
	int x1 = x0 + fm.stringWidth(str.substring(0, problemStart));
	int w1 = fm.stringWidth(str.substring(problemStart, problemEnd));
	drawProblemMark(g2, x1, y + fm.getDescent(), w1); 
      }
    }
  }

  protected static final String tokenDelimiter = " \t\n,.;:{}[]()^~!%&*-+=|/<>?";       

  public static List<Mark> markString(String str, int begin, int end) { 
    //System.out.println("TextMarker.markString()[" + begin + ":" + end +"] " + str);

    if (str != null && 
	begin >= 0 && end >=0 &&  
	begin < end) { 
      int len = str.length();
      if (begin < len && end <= len) { 
	// find string/char literal 
	char quote = '\u0000';
        int k = str.indexOf("\'", begin); // start of the first char literal
        int l = str.indexOf("\"", begin); // start of the first string literal
	if (k >= end) k = -1;
	if (l >= end) l = -1;
        if (k >= 0 && l >= 0) {
          if (k < l) {
            quote = '\'';
          } else {
            k = l;
            quote = '\"';
          }
        } else if (k >= 0) {
          quote = '\'';
        } else if (l >= 0) {
          k = l;
          quote = '\"';
        }
	// k = start of the first char/string literal
        if (k >= 0) { 
	  // char/string literal 
          l = JavaLangUtil.endOfQuote(str, quote, k + 1);
	  List<Mark> marks = new ArrayList<Mark>();
	  List<Mark> m1 = markString(str, begin, k);
	  if (m1 != null) { 
	    marks.addAll(m1);
	  }
          if (l >= 0 && l < end) {
	    marks.add(new Mark(k, l, STRING_LITERAL));
	    m1 = markString(str, l, end);
	    if (m1 != null) { 
	      marks.addAll(m1);
	    }
          } else {
	    marks.add(new Mark(k, end, STRING_LITERAL));
          }
	  return marks; 
        } else { 
	  String seg = str.substring(begin, end);
	  char sq0 = '\u00AB';
	  char sq1 = '\u00BB';
	  k = seg.indexOf(sq0);
	  if (k >= 0) { 
	    l = seg.indexOf(sq1, k + 1);
	  }
	  if (k >= 0 && l > k) {
	    List<Mark> marks = new ArrayList<Mark>();
	    List<Mark> m1 = markString(str, begin, k);
	    if (m1 != null) {
	      marks.addAll(m1);
	    }
	    marks.add(new Mark(k, l + 1, STEREOTYPE));
	    m1 = markString(str, l + 1, end);
	    if (m1 != null) { 
	      marks.addAll(m1);
	    }
	    return marks;
	  } else { 
	    // no string/char literal 
	    StringTokenizer st = new StringTokenizer(seg,
						     tokenDelimiter, true);
	    List<Mark> marks = new ArrayList<Mark>();
	    int i = begin; 
	    int j; 
	    String token; 
	    while (st.hasMoreTokens()) {
	      token = st.nextToken();
	      j = i + token.length(); 
	      if (JavaReservedWords.isReservedWord(token)) { 
		marks.add(new Mark(i, j, RESERVED_WORD));
	      } else if (JavaReservedWords.isModifier(token)) {
		marks.add(new Mark(i, j, MODIFIER));
	      } else if (JavaReservedWords.isTypeName(token)) {
		marks.add(new Mark(i, j, TYPE));
	      } else if (JavaLangUtil.isNumber(token)) {
		marks.add(new Mark(i, j, NUMBER));
	      } else if (JavaReservedWords.isConstant(token)) {
		marks.add(new Mark(i, j, CONSTANT));
	      } else if (JavaLangUtil.isClassName(token)) {
		marks.add(new Mark(i, j, CLASS_NAME));
	      } else {
		marks.add(new Mark(i, j, DEFAULT_STYLE));
	      }
	      i = j;
	    }
	    return marks; 
	  }
	}

      }
    }
    return null;
  }

  public static Style getStyle(int s) {
    if (s >= 0 && s < styles.length) { 
      return styles[s]; 
    }
    return null;
  }

  public static Color getColor(int s) {
    Style st = getStyle(s);
    if (st != null) { 
      return st.color;
    }
    return null;
  }

  static Style[] styles = { 
    new Style(),                                                // DEFAULT_STYLE
    new Style(new Color(0,    0x66, 0), false, true, false),    // COMMENT 
    new Style(new Color(0,    0xBB, 0)),                        // STRING_LITERAL
    new Style(new Color(0x33, 0x33, 0xFF), true, false, false), // RESERVED_WORD    
    new Style(new Color(0x66, 0,    0)),                        // MODIFIER
    new Style(new Color(0x33, 0x99, 0x99)),                     // TYPE 
    new Style(new Color(0x99, 0,    0)),                        // NUMBER 
    new Style(new Color(0,    0xFF, 0xFF)),                     // CONSTANT 
    new Style(new Color(0x33, 0x99, 0x99)),                     // CLASS_NAME
    new Style(new Color(0x80, 0x80, 0x80)),                     // OBJECT_NAME
    new Style(new Color(0x80, 0x80, 0x80)),                     // VARIABLE_NAME
    new Style(new Color(0x80, 0x80, 0x80)),                     // METHOD_NAME  
    new Style(new Color(0x80, 0x80, 0x80)),                     // OPERATOR
    new Style(new Color(0x80, 0x00, 0x80)),                     // STEREOTYPE
    new Style(new Color(0xFF, 0x00, 0x00)),                     // EXCEPTION
  };

  public static void drawProblemMark(Graphics2D g2, int x, int y, int width) { 
    Color c = g2.getColor();
    g2.setColor(Color.red); 
    //g2.drawLine(x, y, x + width, y);
    int w = 4, h = 1; 
    boolean up = true;
    for (int x0 = x; x0 < x + width; x0 += w) { 
      int start = (up ? 0 : 180);
      g2.drawArc(x0, y - h, w, h * 2, start, 180); 
      up = !up; 
    }
    g2.setColor(c);
  }

} 